/*
 * Copyright (C) 2010-2014 Enrique Lara (enrique.posix2.lara@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package app.transittool;

import android.app.Activity;
import android.app.ProgressDialog;

import android.content.Context;
import android.content.Intent;

import android.location.Location;

import android.os.Bundle;

import android.view.ContextMenu;

import android.view.ContextMenu.ContextMenuInfo;

import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;

import android.view.View.OnClickListener;

import android.view.ViewGroup;

import android.widget.AdapterView;

import android.widget.AdapterView.OnItemClickListener;

import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

import app.transittool.model.Agency;
import app.transittool.model.Alarm;
import app.transittool.model.App;
import app.transittool.model.DataRetrievalException;
import app.transittool.model.Stop;

import app.transittool.provider.Tables.Alarms;

import app.transittool.util.Constants;
import app.transittool.util.Log;

import java.io.Serializable;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.TreeSet;

public class NearbyStops extends Activity implements OnItemClickListener {
    private static final String TAG = "NearbyStops";
    public static final int MENU_ITEM_INSERT = Menu.FIRST;
    public static final int MENU_ITEM_VIEW = Menu.FIRST + 1;
    public static final int MENU_ITEM_VIEW_NEARBY_STOPS = Menu.FIRST + 2;
    private static final long REFRESH_INTERVAL_MILLIS = 20000;
    private static final int ON_RESUME_REFRESH_DELAY_MILLIS = 2000;
    private static final int MILE_IN_METERS = 1609;
    private static final double YARDS_PER_METER = 1.0936d;
    private Agency[] agencies = null;
    private Stop s = null;
    private LayoutInflater layoutInflater;
    private ListView stopsView = null;
    private StopAdapter adapter;
    private TextView stationView;
    private long lastRefreshedTimeMillis = -1;
    private TextView lastRefreshedView;
    private ProgressDialog loadingDialog = null;
    private MyDeviceLocationListener listener;
    private FlingLeftRightGestureListener flingListener;

    //1001888  29th st + #2745
    //
    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.nearby_stops);
        setTitle(getText(R.string.nearby_stops));

        if (getIntent().getData() != null) {
            final Alarm a = App.loadAlarm(getIntent().getData(), this);

            try {
                s = a.getAgency().getStopByCode(a.getStopCode());
            } catch (final DataRetrievalException e) {
                Android.showLongToast(this, R.string.could_not_locate_stop, a.getStopCode());
                Log.w(TAG, "Problems obtaining data for stop", e);
            }
        } else if (getIntent().getExtras() != null) {
            final int agencyId = getIntent().getExtras().getInt(Alarms.AGENCY_ID);
            final String apiCode = getIntent().getExtras().getString(Alarms.STOP);

            final Agency agency = App.getAgency(agencyId, this);

            try {
                s = agency.getStopByCode(apiCode);
            } catch (final DataRetrievalException e) {
                Android.showLongToast(this, R.string.could_not_locate_stop, apiCode);
                Log.w(TAG, "Problems obtaining data for stop", e);
            }
        } else {
            listener = new MyDeviceLocationListener(this);
        }

        agencies = App.getAllAgencies(this);

        initButtons();

        stationView = (TextView) findViewById(R.id.station_name);
        stationView.setText(R.string.location_tbd);

        final View v = findViewById(R.id.view_station_on_map);
        v.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(final View v) {
                    viewStationOnMap(NearbyStops.this);
                }
            });

        lastRefreshedView = (TextView) findViewById(R.id.last_refreshed);

        //
        loadingDialog = new ProgressDialog(this);
        loadingDialog.setMessage(getText(R.string.loading));
        loadingDialog.setIndeterminate(true);
        loadingDialog.setCancelable(true);
        loadingDialog.show();

        adapter = new StopAdapter();

        stopsView = (ListView) findViewById(R.id.stops);
        stopsView.setOnCreateContextMenuListener(this);
        stopsView.setOnItemClickListener(this);
        stopsView.setAdapter(adapter);

        flingListener = new FlingLeftRightGestureListener(this, Constants.ACTION_ALL, Constants.ACTION_ABOUT);
    }

    class MyDeviceLocationListener extends DeviceLocationListener {
        public MyDeviceLocationListener(final Context c) {
            super(c);
        }

        @Override
        public void onLocationChanged(final Location arg0) {
            super.onLocationChanged(arg0);

            refreshAsync(0);
        }
    }

    private void initButtons() {
        Button b = (Button) findViewById(R.id.done);
        b.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(final View v) {
                    done();
                }
            });
        b = (Button) findViewById(R.id.refresh);
        b.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(final View v) {
                    refreshAsync(0);
                }
            });
    }

    @Override
    protected void onResume() {
        super.onResume();

        layoutInflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        if (listener != null) {
            listener.startListening();
        }

        refreshAsync(ON_RESUME_REFRESH_DELAY_MILLIS);
    }

    @Override
    public boolean dispatchTouchEvent(final MotionEvent ev) {
        flingListener.dispatchTouchEvent(ev);

        return super.dispatchTouchEvent(ev);
    }

    public Location getOriginLocation() {
        if (s != null) {
            return Android.toLocation(s);
        }

        return listener.getLocation();
    }

    private void done() {
        setResult(RESULT_OK);
        finish();
    }

    protected void refreshAsync(final long delayTimeMillis) {
        final Thread t = new Thread() {
            @Override
            public void run() {
                if (delayTimeMillis > 0) {
                    // Sleep a short spell before refreshing.
                    try {
                        Thread.sleep(delayTimeMillis);
                    } catch (final InterruptedException e) {
                        App.showError(NearbyStops.this, "Problems delaying refresh task.", e);
                    }
                }

                refresh();
            }
        };

        t.start();
    }

    private void refresh() {
        synchronized (lastRefreshedView) {
            if ((System.currentTimeMillis() - lastRefreshedTimeMillis) < REFRESH_INTERVAL_MILLIS) {
                Log.d(TAG, "Last refresh within " + REFRESH_INTERVAL_MILLIS + " ms.");

                return;
            }

            try {
                final Date lastRefreshed = new Date();
                lastRefreshedTimeMillis = System.currentTimeMillis();

                final Location location = getOriginLocation();

                if (location == null) {
                    Android.showLongToast(NearbyStops.this, R.string.could_not_obtain_location);

                    return;
                }

                final CharSequence originName;

                if (s != null) {
                    originName = s.getName();
                } else {
                    originName = MessageFormat.format(getText(R.string.location_lat_lon).toString(), location.getLatitude(), location.getLongitude());
                }

                // FARRAGUT_WEST("C03", "Farragut West", 38.9013128941,
                // -77.0406954151, BLUE, ORANGE),
                // double lat = 38.9013128941;
                // double lon = -77.0406954151;
                // lat = 38.86327;
                // lon = -77.083597;
                final double lat = location.getLatitude();
                final double lon = location.getLongitude();

                final Collection<NearbyStopResult> stops = new TreeSet<NearbyStopResult>(new NearbyStopResultComparator());

                for (final Agency agency : agencies) {
                    final ArrayList<Stop> vehicles = agency.getNearbyStops(lat, lon, MILE_IN_METERS / 2);

                    // mile
                    for (final Stop stop : vehicles) {
                        stops.add(new NearbyStopResult(stop, location));
                    }
                }

                NearbyStops.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            lastRefreshedView.setText(Android.formatDate(NearbyStops.this, lastRefreshed));
                            stationView.setText(originName);
                            adapter.setStops(stops);
                            adapter.notifyDataSetChanged();
                            loadingDialog.hide();
                        }
                    });
            } catch (final DataRetrievalException e) {
                App.showError(NearbyStops.this, "Problems obtaining stop data", e);
            }
        }
    }

    public void viewStationOnMap(final Activity c) {
        final Location l = getOriginLocation();

        if (Settings.isUseStreetView(c)) {
            Android.gotoStreetView(c, l.getLatitude(), l.getLongitude());
        } else {
            Android.gotoMap(c, l.getLatitude(), l.getLongitude());
        }
    }

    // XXX consider making a model object with a stop field.
    static class NearbyStopResult {
        private final Stop stop;
        private final double dist;

        public NearbyStopResult(final Stop stop, final Location originLocation) {
            this.stop = stop;
            dist = originLocation.distanceTo(Android.toLocation(stop));
        }

        public Stop getStop() {
            return stop;
        }

        public double getDistance() {
            return dist;
        }
    }

    static class NearbyStopResultComparator implements Comparator<NearbyStopResult>, Serializable {
        private static final long serialVersionUID = 1518065184463917065L;

        @Override
        public int compare(final NearbyStopResult stop1, final NearbyStopResult stop2) {
            if (stop1 == null) {
                if (stop2 == null) {
                    return 0;
                } else {
                    return -1;
                }
            } else if (stop2 == null) {
                return 1;
            } else {
                if (stop1.dist < stop2.dist) {
                    return -1;
                } else if (stop1.dist > stop2.dist) {
                    return 1;
                } else {
                    return 0;
                }
            }
        }
    }

    private class StopAdapter extends BaseAdapter {
        private final ArrayList<NearbyStopResult> items = new ArrayList<NearbyStopResult>();

        public StopAdapter() {
        }

        public synchronized void setStops(final Collection<?extends NearbyStopResult> stops) {
            items.clear();
            items.addAll(stops);
        }

        @Override
        public synchronized int getCount() {
            return items.size();
        }

        @Override
        public synchronized Object getItem(final int position) {
            return items.get(position);
        }

        @Override
        public synchronized long getItemId(final int position) {
            return position;
        }

        @Override
        public synchronized View getView(final int position, final View v, final ViewGroup parent) {
            View view = null;

            final NearbyStopResult item = items.get(position);
            final Stop stop = item.getStop();

            if (v != null) {
                view = v;
            } else {
                view = layoutInflater.inflate(R.layout.list_item_nearby_bus_stop, parent, false);
            }

            // Routes
            final String[] routeNames = stop.getRouteNames();
            final StringBuilder routes = new StringBuilder();

            for (int i = 0; i < routeNames.length; i++) {
                if (i > 0) {
                    routes.append(",");
                }

                routes.append(routeNames[i]);
            }

            Android.cptxt(view, R.id.station_code, stop.getCode());
            Android.cptxt(view, R.id.headsign, stop.getName());
            Android.cptxt(view, R.id.station_routes, routes);
            Android.cptxt(view, R.id.station_distance, distanceToString(item.getDistance()));

            return view;
        }
    }

    public static String distanceToString(final double distance) {
        // round to a whole number, shaving off the "ones" units.
        final long yards = Math.round((YARDS_PER_METER * distance) / 10d) * 10;

        return Long.toString(yards) + " yds";
    }

    @Override
    public boolean onCreateOptionsMenu(final Menu menu) {
        super.onCreateOptionsMenu(menu);

        if (s != null) {
            menu.add(0, MENU_ITEM_VIEW, 0, R.string.view_alarm).setShortcut('4', 'v').setIcon(android.R.drawable.ic_menu_view);
        }

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        switch (item.getItemId()) {
            case MENU_ITEM_VIEW:
                return s.gotoView();

            default:
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public final void onCreateContextMenu(final ContextMenu menu, final View view, final ContextMenuInfo menuInfo) {
        AdapterView.AdapterContextMenuInfo info;

        try {
            info = (AdapterView.AdapterContextMenuInfo) menuInfo;
        } catch (final ClassCastException e) {
            Log.e(TAG, "bad menuInfo", e);

            return;
        }

        final Stop t = ((NearbyStopResult) stopsView.getAdapter().getItem(info.position)).getStop();

        if (t == null) {
            // For some reason the requested item isn't available, do nothing
            return;
        }

        menu.setHeaderTitle(t.getName());
        menu.add(0, MENU_ITEM_VIEW, 0, R.string.view_stop);
        menu.add(0, MENU_ITEM_VIEW_NEARBY_STOPS, 0, R.string.nearby_stops);

        // menu.add(0, MENU_ITEM_INSERT, 0, R.string.add_alarm_for_station);
    }

    @Override
    public final boolean onContextItemSelected(final MenuItem item) {
        AdapterView.AdapterContextMenuInfo info;

        try {
            info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
        } catch (final ClassCastException e) {
            Log.e(TAG, "bad menuInfo", e);

            return false;
        }

        final Stop ns = ((NearbyStopResult) stopsView.getAdapter().getItem(info.position)).getStop();

        switch (item.getItemId()) {
            case MENU_ITEM_VIEW:
                return ns.gotoView();

            case MENU_ITEM_VIEW_NEARBY_STOPS:
                return ns.gotoViewNearbyStops();

            default:
                return false;
        }
    }

    @Override
    public final void onItemClick(final AdapterView<?> l, final View v, final int position, final long id) {
        Log.d(TAG, "onItemClick");

        final Stop stop = ((NearbyStopResult) l.getAdapter().getItem(position)).getStop();

        final String action = getIntent().getAction();

        if (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action)) {
            // XXX if alarm, add uri?
            setResult(RESULT_OK, new Intent()); // XXX what is this doing?
        } else {
            loadingDialog.show();
            stop.gotoView();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();

        if (listener != null) {
            listener.stopListening();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();

        if (loadingDialog != null) {
            loadingDialog.hide();
        }
    }
}
